Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit e2c1cd37664521ffc53ca38d1b4f39820488ce23


Parents : 1710740
Author : Mark Qvist <mark@unsigned.io>
Date : 2025-11-07T19:11:56+01:00

Added support for LoRa PAs and output power mapping according to PA gain curve

Changes

2 files changed, 143 insertions(+), 47 deletions(-)

M Utilities.h +97 -47
M sx126x.cpp +46

Diff

diff --git a/Utilities.h b/Utilities.h
index 79546cd..eaf71db 100644
--- a/Utilities.h
+++ b/Utilities.h
@@ -1263,54 +1263,104 @@ int getTxPower() {
return (int)txp;
}
+#if HAS_LORA_PA
+ const int tx_gain[PA_GAIN_POINTS] = {PA_GAIN_VALUES};
+#endif
+
+int map_target_power_to_modem_output(int target_tx_power) {
+ #if HAS_LORA_PA
+ int modem_output_dbm = -9;
+ for (int i = 0; i < PA_GAIN_POINTS; i++) {
+ int gain = tx_gain[i];
+ int effective_output_dbm = i + gain;
+ printf("At %d dBm modem output, gain is %d dBm, effective output is %d\n", i, gain, effective_output_dbm);
+ if (effective_output_dbm > target_tx_power) {
+ int diff = effective_output_dbm - target_tx_power;
+ modem_output_dbm = -1*diff;
+ break;
+ } else if (effective_output_dbm == target_tx_power) {
+ modem_output_dbm = i; break;
+ } else if (i == PA_GAIN_POINTS-1) {
+ int diff = target_tx_power - effective_output_dbm;
+ modem_output_dbm = i+diff; break;
+ }
+ }
+ #else
+ int modem_output_dbm = target_tx_power;
+ #endif
+
+ return modem_output_dbm;
+}
+
+int map_modem_output_to_target_power(int modem_output_dbm) {
+ #if HAS_LORA_PA
+ if (modem_output_dbm < 0) { modem_output_dbm = 0; }
+ if (modem_output_dbm >= PA_GAIN_POINTS) { modem_output_dbm = PA_GAIN_POINTS-1; }
+ int gain = tx_gain[modem_output_dbm];
+ int target_tx_power = modem_output_dbm+gain;
+ #else
+ int target_tx_power = modem_output_dbm;
+ #endif
+
+ return target_tx_power;
+}
+
void setTXPower() {
if (radio_online) {
- if (model == MODEL_11) LoRa->setTxPower(lora_txp, PA_OUTPUT_RFO_PIN);
- if (model == MODEL_12) LoRa->setTxPower(lora_txp, PA_OUTPUT_RFO_PIN);
-
- if (model == MODEL_C6) LoRa->setTxPower(lora_txp, PA_OUTPUT_RFO_PIN);
- if (model == MODEL_C7) LoRa->setTxPower(lora_txp, PA_OUTPUT_RFO_PIN);
-
- if (model == MODEL_A1) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_A2) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_A3) LoRa->setTxPower(lora_txp, PA_OUTPUT_RFO_PIN);
- if (model == MODEL_A4) LoRa->setTxPower(lora_txp, PA_OUTPUT_RFO_PIN);
- if (model == MODEL_A5) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_A6) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_A7) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_A8) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_A9) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_AA) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_AC) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
-
- if (model == MODEL_BA) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_BB) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_B3) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_B4) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_B8) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_B9) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
-
- if (model == MODEL_C4) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_C9) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_C5) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_CA) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
-
- if (model == MODEL_D4) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_D9) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
-
- if (model == MODEL_DB) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_DC) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
-
- if (model == MODEL_DD) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_DE) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
-
- if (model == MODEL_E4) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_E9) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_E3) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_E8) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
-
- if (model == MODEL_FE) LoRa->setTxPower(lora_txp, PA_OUTPUT_PA_BOOST_PIN);
- if (model == MODEL_FF) LoRa->setTxPower(lora_txp, PA_OUTPUT_RFO_PIN);
+ int mapped_lora_txp = map_target_power_to_modem_output(lora_txp);
+
+ #if HAS_LORA_PA
+ int real_lora_txp = map_modem_output_to_target_power(mapped_lora_txp);
+ lora_txp = real_lora_txp;
+ #endif
+
+ if (model == MODEL_11) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN);
+ if (model == MODEL_12) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN);
+
+ if (model == MODEL_C6) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN);
+ if (model == MODEL_C7) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN);
+
+ if (model == MODEL_A1) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_A2) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_A3) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN);
+ if (model == MODEL_A4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN);
+ if (model == MODEL_A5) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_A6) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_A7) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_A8) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_A9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_AA) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_AC) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+
+ if (model == MODEL_BA) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_BB) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_B3) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_B4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_B8) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_B9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+
+ if (model == MODEL_C4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_C9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_C5) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_CA) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_C8) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+
+ if (model == MODEL_D4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_D9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+
+ if (model == MODEL_DB) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_DC) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+
+ if (model == MODEL_DD) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_DE) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+
+ if (model == MODEL_E4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_E9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_E3) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_E8) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+
+ if (model == MODEL_FE) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN);
+ if (model == MODEL_FF) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN);
}
}
@@ -1507,7 +1557,7 @@ bool eeprom_product_valid() {
#if PLATFORM == PLATFORM_AVR
if (rval == PRODUCT_RNODE || rval == PRODUCT_HMBRW) {
#elif PLATFORM == PLATFORM_ESP32
- if (rval == PRODUCT_RNODE || rval == BOARD_RNODE_NG_20 || rval == BOARD_RNODE_NG_21 || rval == PRODUCT_HMBRW || rval == PRODUCT_TBEAM || rval == PRODUCT_T32_10 || rval == PRODUCT_T32_20 || rval == PRODUCT_T32_21 || rval == PRODUCT_H32_V2 || rval == PRODUCT_H32_V3 || rval == PRODUCT_TDECK_V1 || rval == PRODUCT_TBEAM_S_V1 || rval == PRODUCT_XIAO_S3) {
+ if (rval == PRODUCT_RNODE || rval == BOARD_RNODE_NG_20 || rval == BOARD_RNODE_NG_21 || rval == PRODUCT_HMBRW || rval == PRODUCT_TBEAM || rval == PRODUCT_T32_10 || rval == PRODUCT_T32_20 || rval == PRODUCT_T32_21 || rval == PRODUCT_H32_V2 || rval == PRODUCT_H32_V3 || rval == PRODUCT_H32_V4 || rval == PRODUCT_TDECK_V1 || rval == PRODUCT_TBEAM_S_V1 || rval == PRODUCT_XIAO_S3) {
#elif PLATFORM == PLATFORM_NRF52
if (rval == PRODUCT_RAK4631 || rval == PRODUCT_HELTEC_T114 || rval == PRODUCT_TECHO || rval == PRODUCT_HMBRW) {
#else

diff --git a/sx126x.cpp b/sx126x.cpp
index 18ae7e2..c1e7c20 100644
--- a/sx126x.cpp
+++ b/sx126x.cpp
@@ -342,12 +342,43 @@ int sx126x::begin(long frequency) {
setModulationParams(_sf, _bw, _cr, _ldro);
setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode);
+ #if HAS_LORA_PA
+ #if LORA_PA_GC1109
+ // Enable Vfem_ctl for supply to
+ // PA power net.
+ pinMode(LORA_PA_PWR_EN, OUTPUT);
+ digitalWrite(LORA_PA_PWR_EN, HIGH);
+
+ // Enable PA LNA and TX standby
+ pinMode(LORA_PA_CSD, OUTPUT);
+ digitalWrite(LORA_PA_CSD, HIGH);
+
+ // Keep PA CPS low until actual
+ // transmit. Does it save power?
+ // Who knows? Will have to measure.
+ pinMode(LORA_PA_CPS, OUTPUT);
+ digitalWrite(LORA_PA_CPS, LOW);
+
+ // On Heltec V4, the PA CTX pin
+ // is driven by the SX1262 DIO2
+ // pin directly, so we do not
+ // need to manually raise this.
+ #endif
+ #endif
+
return 1;
}
void sx126x::end() { sleep(); SPI.end(); _preinit_done = false; }
int sx126x::beginPacket(int implicitHeader) {
+ #if HAS_LORA_PA
+ #if LORA_PA_GC1109
+ // Enable PA CPS for transmit
+ digitalWrite(LORA_PA_CPS, HIGH);
+ #endif
+ #endif
+
standby();
if (implicitHeader) { implicitHeaderMode(); }
else { explicitHeaderMode(); }
@@ -432,6 +463,9 @@ int ISR_VECT sx126x::currentRssi() {
uint8_t byte = 0;
executeOpcodeRead(OP_CURRENT_RSSI_6X, &byte, 1);
int rssi = -(int(byte)) / 2;
+ #if HAS_LORA_LNA
+ rssi -= LORA_LNA_GAIN;
+ #endif
return rssi;
}
@@ -446,6 +480,9 @@ int ISR_VECT sx126x::packetRssi() {
uint8_t buf[3] = {0};
executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3);
int pkt_rssi = -buf[0] / 2;
+ #if HAS_LORA_LNA
+ pkt_rssi -= LORA_LNA_GAIN;
+ #endif
return pkt_rssi;
}
@@ -550,6 +587,13 @@ void sx126x::onReceive(void(*callback)(int)){
}
void sx126x::receive(int size) {
+ #if HAS_LORA_PA
+ #if LORA_PA_GC1109
+ // Disable PA CPS for receive
+ digitalWrite(LORA_PA_CPS, LOW);
+ #endif
+ #endif
+
if (size > 0) {
implicitHeaderMode();
_payloadLength = size;
@@ -584,6 +628,8 @@ void sx126x::enableTCXO() {
uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF};
#elif BOARD_MODEL == BOARD_TECHO
uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF};
+ #elif BOARD_MODEL == BOARD_HELTEC32_V4
+ uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF};
#endif
executeOpcode(OP_DIO3_TCXO_CTRL_6X, buf, 4);
#endif


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────